package cl.scene.badseeds;

import cl.scene.libbencode.DictionaryType;
import com.mongodb.*;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.MultiMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.StringTokenizer;

/**
 * Created by IntelliJ IDEA.
 * User: Patricio
 * Date: 29-11-2010
 * Time: 12:54:37 PM
 */
public class AnnounceHandler extends AbstractHandler {

    private DB database;

    public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {

        DictionaryType response;
        BasicDBObject hash;
        BasicDBObject peer;
        AnnounceRequest announceRequest;
        try {
            announceRequest = parseRequest(request);
            peer = checkPassKey(announceRequest);
            hash = checkHash(announceRequest);
            checkAndHandleEvent(announceRequest, hash);
            savePeerStats(announceRequest, hash, peer);
            response = buildAnnounceResponse(announceRequest, hash, peer);
            updatePeersOnHash(announceRequest, hash, peer);
            System.out.println(request.getQueryString());
        } catch (Exception e) {
            response = new DictionaryType();
            response.add("failure reason", e.getMessage());
            response.add("failure code", "100");
            e.printStackTrace();
        }


        httpServletResponse.setContentType("text/html;charset=utf-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        response.encodeAndWrite(httpServletResponse.getOutputStream());
        httpServletResponse.flushBuffer();

    }

    private void checkAndHandleEvent(AnnounceRequest announceRequest, BasicDBObject hash) {
        if (announceRequest.getEvent() == null) return;

        if (announceRequest.getEvent().equals("stopped")) {
            BasicDBObject query = new BasicDBObject();
            BasicDBObject peerInfo = new BasicDBObject("peer_id", announceRequest.getPeerId());
            query.put("$pull", new BasicDBObject("peer_list", peerInfo));
            DBCollection hashes = database.getCollection("hashes");
            hashes.update(new BasicDBObject("info_hash", announceRequest.getInfoHash()), query);
        }
    }

    private DictionaryType buildAnnounceResponse(AnnounceRequest announceRequest, BasicDBObject hash, BasicDBObject peer) {
        DBCollection hashes = database.getCollection("hashes");

        BasicDBList peerList = (BasicDBList) hash.get("peer_list");
        DictionaryType response = new DictionaryType();
        String peersString = "";


        for (Object peerInfoO : peerList) {
            BasicDBObject peerInfo = (BasicDBObject) peerInfoO;
            peersString = peersString.concat(new String((byte[]) peerInfo.get("compact")));
        }
        response.add("interval", "60");
        response.add("peers", peersString);

        return response;
    }

    private void updatePeersOnHash(AnnounceRequest announceRequest, BasicDBObject hash, BasicDBObject peer) {
        if(("stopped").equals(announceRequest.getEvent())) return;
        DBCollection hashes = database.getCollection("hashes");

        BasicDBObject peerInfo = new BasicDBObject();
        BasicDBObject query = new BasicDBObject();

        peerInfo.put("peer_id", announceRequest.getPeerId());
        peerInfo.put("ip", announceRequest.getIp());
        peerInfo.put("port", announceRequest.getPort());
        byte[] compact = new byte[6];
        StringTokenizer tokenizer = new StringTokenizer(announceRequest.getIp(), ".", false);

        int aux = 0;

        while (tokenizer.hasMoreElements()) {
            String number = (String) tokenizer.nextElement();
            compact[aux++] = (byte) Integer.parseInt(number);
        }

        compact[4] = (byte) (announceRequest.getPort() >> 8 & 0xff);
        compact[5] = (byte) (announceRequest.getPort() & 0xff);

        peerInfo.put("compact", compact);

        query.put("$addToSet", new BasicDBObject("peer_list", peerInfo));

        hashes.update(hash, query);
    }

    private void savePeerStats(AnnounceRequest announceRequest, BasicDBObject hash, BasicDBObject peer) {

        DBCollection peers = database.getCollection("peers");

        BasicDBObject stats = new BasicDBObject();
        stats.put("snatchlist." + new String(announceRequest.getInfoHash()) + ".stats.uploaded", announceRequest.getUploaded());
        stats.put("snatchlist." + new String(announceRequest.getInfoHash()) + ".stats.downloaded", announceRequest.getUploaded());

        BasicDBObject query = new BasicDBObject("$inc", stats);

        peers.update(peer, query, true, false);


    }

    private BasicDBObject checkHash(AnnounceRequest announceRequest) throws Exception {
        DBCollection hashes = database.getCollection("hashes");
        BasicDBObject query = new BasicDBObject();
        query.put("info_hash", announceRequest.getInfoHash());
        int numwant = announceRequest.getNumwant();
        BasicDBObject hash = (BasicDBObject) hashes.findOne(query, new BasicDBObject("peer_list", new BasicDBObject("$slice", -numwant)));
        if (hash == null) {
            hash = new BasicDBObject();
            hash.put("info_hash", announceRequest.getInfoHash());
            hash.put("peer_list", new BasicDBList());
            hashes.insert(hash);
        }

        return hash;
    }

    private BasicDBObject checkPassKey(AnnounceRequest announceRequest) throws Exception {
        DBCollection peers = database.getCollection("peers");
        BasicDBObject query = new BasicDBObject();
        query.put("_id", announceRequest.getPassKey());

        BasicDBObject peer = (BasicDBObject) peers.findOne(query);

        if (peer == null) {
            peer = new BasicDBObject();
            peer.put("_id", announceRequest.getPassKey());
            peer.put("active", true);
            peers.insert(peer);
            return query;
        } else if (!peer.getBoolean("active", false)) {
            throw new Exception("access denied: pass key disabled");
        }

        return peer;
    }

    private AnnounceRequest parseRequest(Request request) throws Exception {
        AnnounceRequest ar;

        if (request.getRequestURI().length() < 35) {
            throw new Exception("invalid request");
        }

        MultiMap m = new MultiMap<String>();
        org.eclipse.jetty.util.UrlEncoded.decodeTo(request.getQueryString(), m, "ASCII");

        ar = new AnnounceRequest(m);
        ar.setPassKey(request.getRequestURI().substring(3, 35));
        if (ar.getIp() == null) {
            ar.setIp(request.getRemoteAddr());
        }


        return ar;
    }

    public DB getDatabase() {
        return database;
    }

    public void setDatabase(DB database) {
        this.database = database;
    }
}
